﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Documents;
using Snowdreamist.Text.UBB;

namespace Snowdreamist.Windows.Document.Adapter
{
    public class UBBDocumentAdapter : DocumentAdaperBase
    {
        public override Type TargetType
        {
            get
            {
                return typeof(UBBDocument);
            }
        }

        public override RichTextDocument Convert(object source)
        {
            throw new InvalidOperationException("Need element adapters");
        }

        public override RichTextDocument Convert(object source, IDictionary<string, IElementAdapter> elementAdapters)
        {
            if (source == null)
                throw new ArgumentNullException("source");
            if (elementAdapters == null)
                throw new ArgumentNullException("elementAdapters");
            if (!(source is UBBDocument))
                throw new ArgumentException("Source invalid type");
            // convert
            UBBDocument ubbDoc = source as UBBDocument;
            // generate doc
            RichTextDocument rtDoc = new RichTextDocument();

            try
            {
                var r = this.Convert(ubbDoc.Root, null, elementAdapters);

                if (r is Block)
                {
                    rtDoc.Blocks.Add(r as Block);
                    return rtDoc;
                }
                else if (r is IEnumerable<Block>)
                {
                    rtDoc.Blocks.AddRange(r as IEnumerable<Block>);
                    return rtDoc;
                }
                else
                {
                    rtDoc.Blocks.Add(new BlockUIContainer(this.GenerateErrorObject(new ArgumentException("Expect Block or IEnumerable<Block>"), typeof(RootElement))));
                    return rtDoc;
                }
            }
            catch (Exception error)
            {
                rtDoc.Blocks.Add(new BlockUIContainer(this.GenerateErrorObject(error, typeof(UBBDocument))));
                return rtDoc;
            }
            
        }
        /// <summary>
        /// Convert ubb document elements to rich text document elements
        /// Principles:
        ///     1. If error occurs, please throw exception
        ///     2. When convert child elements, please catch any exceptions
        /// </summary>
        /// <param name="element"></param>
        /// <param name="metadata"></param>
        /// <param name="elementAdapters"></param>
        /// <returns></returns>
        protected virtual object Convert(IUBBElement element, UBBContentMetadata metadata, IDictionary<string, IElementAdapter> elementAdapters)
        {
            if (element == null)
                throw new ArgumentNullException("element");
            if (elementAdapters == null)
                throw new ArgumentNullException("elementAdapters");
            // get identity
            var id = this.GetIdentity(element, metadata);
            if (!elementAdapters.ContainsKey(id) || elementAdapters[id] == null)
            {
                // id not found
                throw new InvalidOperationException("Adapter for " + id + " not found");
            }
            else
            {
                // convert
                var adapter = elementAdapters[id];
                var docElm = adapter.Convert(element, metadata);
                if (docElm == null)
                    throw new InvalidOperationException("Adapter for " + id + " return null");
                // if has child
                if (element is IUBBCollectionElement && (element as IUBBCollectionElement).Count != 0)
                {
                    if (adapter.AcceptChildType == AcceptType.None)
                    {
                        throw new InvalidOperationException("Cannot attach child element(s) to " + id);
                    }
                    else
                    {
                        // children
                        List<object> children = new List<object>();
                        // convert child(ren)
                        var col = element as IUBBCollectionElement;
                        UBBContentMetadata currentMetadata = null;

                        foreach (var e in col)
                        {
                            try
                            {
                                if (e is Snowdreamist.Text.UBB.TextElement)
                                {
                                    var textElm = e as Snowdreamist.Text.UBB.TextElement;
                                    children.Add(textElm.Value);
                                }
                                else
                                {
                                    UBBContentMetadata md = null;
                                    if (this.TryFetchMetadata(e, out md))
                                    {
                                        currentMetadata = md;
                                    }
                                    else
                                    {
                                        md = currentMetadata;
                                        // why copy current metadata?
                                        // In order to clean metadata correctly when an exception is thrown in this.Convert
                                        currentMetadata = null;
                                        var r = this.Convert(e, md, elementAdapters);
                                        // append
                                        children.Add(r);
                                    }
                                }
                            }
                            catch(Exception error)
                            {
                                // generate error object
                                children.Add(this.GenerateErrorObject(error, e.GetType()));
                            }
                        }

                        // append children to current element
                        // proper wrap child elements
                        var wrappedObjects = this.WrapElements(children, adapter.AcceptChildType);
                        adapter.AppendRange(docElm, wrappedObjects);
                    }
                }
                // return
                return docElm;
            }
        }
        /// <summary>
        /// Get identity for ubb element
        /// </summary>
        /// <param name="element"></param>
        /// <param name="metadata"></param>
        /// <returns></returns>
        protected virtual string GetIdentity(IUBBElement element, UBBContentMetadata metadata)
        {
            if (metadata != null && !string.IsNullOrWhiteSpace(metadata.SourceElementIdentity))
            {
                // re-map identity
                return metadata.SourceElementIdentity;
            }
            else
            {
                // default identity: type name
                return element.GetType().Name;
            }
        }
        /// <summary>
        /// Try to fetch ubb metadata from element
        /// </summary>
        /// <param name="element"></param>
        /// <param name="metadata"></param>
        /// <returns></returns>
        protected virtual bool TryFetchMetadata(IUBBElement element, out UBBContentMetadata metadata)
        {
            metadata = null;

            if (element is SizeElement)
            {
                SizeElement sizeElm = element as SizeElement;
                if (sizeElm.Size == 0 && sizeElm.Count == 1 && sizeElm[0] is Snowdreamist.Text.UBB.TextElement)
                {
                    var textElm = sizeElm[0] as Snowdreamist.Text.UBB.TextElement;
                    metadata = UBBContentMetadata.TryDeserialize(textElm.Value);
                    return metadata != null;
                }
                else
                {
                    return false;
                }
            }
            else
            {
                return false;
            }
        }
    }
}
